package com.xr.core.system.core.aop;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.xr.common.axios.AxiosResult;
import com.xr.common.axios.R;
import com.xr.core.system.common.annotation.OperationLogAnno;
import com.xr.core.system.common.base.Result;
import com.xr.core.system.common.enums.OperationType;
import com.xr.core.system.common.utils.CurrentLoginUserUtil;
import com.xr.core.system.common.utils.JsonUtil;
import com.xr.core.system.core.utils.IpUtil;
import com.xr.core.system.entity.exceptionLogModule.ExceptionLog;
import com.xr.core.system.entity.operationLogModule.LogEntity;
import com.xr.core.system.entity.operationLogModule.OperationLog;
import com.xr.core.system.entity.userModule.User;
import com.xr.core.system.service.ExceptionLogService;
import com.xr.core.system.service.OperationLogService;
import com.xr.core.system.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

/**
 * 全局日志
 */
@Aspect
@Component
@Slf4j
public class LogRecordAspect {

    @Autowired
    private OperationLogService operationLogService;

    @Autowired
    private ExceptionLogService exceptionLogService;

    @Autowired
    private UserService userService;

    public String getCurrentLoginUsername() {
        User user = userService.getById(CurrentLoginUserUtil.getUserId());
        return user != null ? user.getName() : "";
    }

    /**
     * 设置操作日志切入点 记录操作日志 在注解的位置切入代码
     */
    @Pointcut("@annotation(operationLogAnno)")
    public void operLogPointCut(OperationLogAnno operationLogAnno) {
    }

    /**
     * 设置操作异常切入点记录异常日志 扫描所有controller包下操作
     */
    @Pointcut("execution(* com..*.controller..*.*(..))")
    public void excludeService() {
    }

    @AfterReturning(value = "operLogPointCut(operationLogAnno)", returning = "keys", argNames = "joinPoint,keys,operationLogAnno")
    public void doAround(JoinPoint joinPoint, Object keys, OperationLogAnno operationLogAnno) {
        OperationLog operationLog = new OperationLog();
        if (keys instanceof Result || keys instanceof AxiosResult || keys instanceof R) {
            // 返回结果
            operationLog.setRespContent(StrUtil.maxLength(JsonUtil.objectToJson(keys), 10000));
        } else {
            // 返回结果
            operationLog.setRespContent("返回数据非JSON格式！");
        }
        Long userId = CurrentLoginUserUtil.getUserId();
        // 请求用户ID
        operationLog.setUserId(userId);
        // 请求用户名称
        operationLog.setUserName(this.getCurrentLoginUsername());
        // 请求IP
        operationLog.setIp(IpUtil.getClientIp());
        setRequestParamsAndMethodName(operationLog, joinPoint);
        processingOperationLog(operationLog, operationLogAnno);
    }

    @Async("taskExecutor")
    void processingOperationLog(OperationLog operationLog, OperationLogAnno operationLogAnno) {
        if (operationLogAnno != null) {
            String module = operationLogAnno.module();
            OperationType type = operationLogAnno.type();
            String desc = operationLogAnno.desc();
            // 操作模块
            operationLog.setModule(module);
            // 操作类型
            operationLog.setType(type.toString());
            // 操作描述
            operationLog.setDescription(desc);
        }
        operationLogService.save(operationLog);
    }

    /**
     * 异常返回通知，用于拦截异常日志信息 连接点抛出异常后执行
     *
     * @param joinPoint 切入点
     * @param e         异常信息
     */
    @AfterThrowing(pointcut = "excludeService()", throwing = "e")
    public void saveExceptionLog(JoinPoint joinPoint, Throwable e) {
        ExceptionLog exceptionLog = new ExceptionLog();
        Long userId = CurrentLoginUserUtil.getUserId();
        // 请求用户ID
        exceptionLog.setUserId(userId);
        // 请求用户名称
        exceptionLog.setUserName(this.getCurrentLoginUsername());
        // 操作员IP
        exceptionLog.setIp(IpUtil.getClientIp());
        setRequestParamsAndMethodName(exceptionLog, joinPoint);
        processingExceptionLog(exceptionLog, e);
    }

    @Async("taskExecutor")
    void processingExceptionLog(ExceptionLog exceptionLog, Throwable e) {
        // 异常名称
        exceptionLog.setName(e.getClass().getName());
        // 异常信息
        exceptionLog.setMessage(StrUtil.maxLength(stackTraceToString(e.getClass().getName(), e.getMessage(), e.getStackTrace()), 10000));
        exceptionLogService.save(exceptionLog);
    }

    /**
     * 转换异常信息为字符串
     *
     * @param exceptionName    异常名称
     * @param exceptionMessage 异常信息
     * @param elements         堆栈信息
     */
    public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
        StringBuilder sb = new StringBuilder();
        sb.append(exceptionName).append(":").append(exceptionMessage).append("\n\t");
        for (StackTraceElement stet : elements) {
            sb.append(stet).append("\n");
        }
        return sb.toString();
    }

    /**
     * 封装 日志信息
     *
     * @param logEntity 日志对象
     * @param joinPoint 切入点
     */
    private void setRequestParamsAndMethodName(LogEntity logEntity, JoinPoint joinPoint) {
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        assert sra != null;
        HttpServletRequest request = sra.getRequest();
        String httpMethod = request.getMethod();
        String queryString = request.getQueryString();
        Object[] args = joinPoint.getArgs();
        String params = "";
        if (args.length > 0) {
            if ("GET".equals(httpMethod) || "DELETE".equals(httpMethod)) {
                params = queryString;
            } else {
                for (int i = 0; i < args.length; i++) {
                    if (args[i] instanceof ServletRequest) {
                        args[i] = ((HttpServletRequest) args[i]).getParameterMap();
                    } else if (args[i] instanceof ServletResponse) {
                        args[i] = "response对象";
                    } else if (args[i] instanceof MultipartFile) {
                        args[i] = "MultipartFile对象";
                    } else if (args[i] instanceof MultipartFile[]) {
                        args[i] = "MultipartFile数组对象";
                    } else if (args[i] instanceof BindingResult) {
                        args[i] = "BindingResult对象";
                    }
                }
                params = JSONUtil.toJsonStr(args);
            }
        }
        // 从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取切入点所在的方法
        Method method = signature.getMethod();
        // 获取请求的类名
        String className = joinPoint.getTarget().getClass().getName();
        // 获取请求的方法名
        String methodName = className + "." + method.getName();
        // 请求参数
        logEntity.setRequestParam(StrUtil.maxLength(params, 10000));
        // 请求方法名
        logEntity.setMethod(methodName);
        // 操作URI
        logEntity.setUri(request.getRequestURI());
    }

}
